package com.huwei.util.cryption;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Objects;

import com.huwei.util.BeanUtil;

/**
 * JWT token 认证工具
 * @author Administrator
 *
 */
public class JWTTokenUtil {
	
	/*
	 * 加密方式
	 */
	final static private String TOKEN_ALG = "HS256";
	/*
	 * token 类型
	 */
	final static private String TOKEN_TYPE = "JWT";

	/*
	 * token级别参数
	 */
	private AuthenConstants authenConstants; 
	
	private JWTTokenUtil(AuthenConstants authenConstants) {
		this.authenConstants = authenConstants;
	}
	
	public AuthenConstants getAuthenConstants() {
		return authenConstants;
	}
	
	/**
	 * 创建token
	 * @param secret 公共密匙
	 * @param tokenId 当前tokenid token的唯一标识
	 * @param tokenType token持有者类型，可分辨是哪类用户访问
	 * @param userInfo 用户信息；
	 * @return
	 */
	public Token createToken(String secret,String tokenId,String tokenType,String userInfo) {
		Token token = new Token();
		//创建头
		Token.Head head = new Token.Head();
		head.setAlg(TOKEN_ALG);
		head.setTyp(TOKEN_TYPE);
		
		//创建载荷
		//签发时间
		long iat = System.currentTimeMillis();
		//过期时间 20 分钟后过期
		long exp = iat + authenConstants.getTokenExpTime();
		//最后存活时间
		long gra = iat + authenConstants.getTokeGraTime();
		Token.Playload playload = new Token.Playload();
		playload.setAud(authenConstants.getAudience()); //接收token的一方
		playload.setIat(iat); //签发时间
		playload.setExp(exp);
		playload.setGra(gra);
		playload.setIss(authenConstants.getIssuser());//签发者
		playload.setJti(tokenId); //token唯一身份标识
		playload.setNbf(iat);//生效时间,立即生效
		playload.setSub(userInfo); //token的所属者,可以存放用户名
		playload.setTyp(tokenType.toString().toUpperCase());
		
		//创建token
		String headBase64 = TokenUtil.headToBase64(head);
		String playloadBase64 = TokenUtil.playloadToBase64(playload);
		String signature = TokenUtil.secretToBase64(headBase64, playloadBase64, secret);//token签名
		
		//token 创建失败
		if(Objects.isNull(headBase64) || Objects.isNull(playloadBase64)
				|| Objects.isNull(signature)) {
			//log
			return null;
		}
		
		String tokenStr = headBase64+"."+playloadBase64+"."+signature; //token字符串
		
		//组装token对象
		token.setHead(head);
		token.setPlayload(playload);
		token.setBase64Head(headBase64);
		token.setBase64PlayLoad(playloadBase64);
		token.setSignature(signature);
		token.setTokenStr(tokenStr);
		return token;
	}
	
	public Token parseToken(String token,String secret) {
		try {
			//判断token是否是合法格式的token串
			if(Objects.isNull(token)) {
				throw new Exception("token串为空!");
			}
			if(Objects.isNull(secret)) {
				throw new Exception("解析token时,token密钥为空!");
			}
			String[] tokens = token.split("\\.");
			if(tokens==null || tokens.length!=3) {
				throw new Exception("非法格式的token串:"+token);
			}
			
			//token分解
			String base64Head = tokens[0].trim(); //token头
			String base64Playload = tokens[1].trim(); //token载荷
			String signature = tokens[2].trim(); //token签名
			//验证签名是否为合法的
			String signaturedStr = TokenUtil.secretToBase64(base64Head, base64Playload, secret.trim()); //token签名
			if(!signature.equals(signaturedStr)) {
				throw new Exception("非法的token:解析token时,token验签失败!");
			}
			
			Token.Head head = TokenUtil.base64ToHead(base64Head);
			Token.Playload playLoad = TokenUtil.base64ToPlayload(base64Playload);
			Token rs = new Token();
			rs.setBase64Head(base64Head);
			rs.setBase64PlayLoad(base64Playload);
			rs.setHead(head);
			rs.setPlayload(playLoad);
			rs.setSignature(signature);
			rs.setTokenStr(token);
			return rs;
		} catch (Exception e) {
			e.printStackTrace();
			//log
			return null;
		}
	}
	
	/**
	 * 验证token是否合法
	 * @param token
	 * @param secret
	 * @return
	 */
	public boolean checkToken(String token ,String secret) {
		Token tk = parseToken(token, secret);
		
		if(Objects.isNull(tk) || Objects.isNull(tk.getSignature()) 
				|| !tk.getSignature().equals(TokenUtil.secretToBase64(tk.getBase64Head(), tk.getBase64PlayLoad(), secret))) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * JWT token 工具builder
	 */
	static public class JWTTokenUtilBuilder {
		static public JWTTokenUtil build(AuthenConstants authenConstants) {
			if(Objects.isNull(authenConstants)) {
				throw new RuntimeException(JWTTokenUtil.class.getName() + " builder failed ,case : AuthenConstants is null");
			}
			
			if(Objects.isNull(authenConstants.getAudience()) || Objects.isNull(authenConstants.getIssuser())
					|| Objects.isNull(authenConstants.getTokeGraTime()) || Objects.isNull(authenConstants.getTokenExpTime())) {
				throw new RuntimeException(JWTTokenUtil.class.getName() + " builder failed ,case : some field is null in AuthenConstants");
			}
			return new JWTTokenUtil(authenConstants);
		}
	}
	
	static public class TokenUtil {
		final static private String CONCAT_CHARACTER = ".";
		
		static public String headToBase64(Token.Head head) {
			return objToBase64(head);
		}
		
		static public String playloadToBase64(Token.Playload playload) {
			return objToBase64(playload);
		}
		
		static public Token.Head base64ToHead(String headBase64){
			return BeanUtil.toBean(Base64.getDecoder().decode(headBase64.getBytes()), Token.Head.class);
		}
		
		static public Token.Playload base64ToPlayload(String playBase64){
			return BeanUtil.toBean(Base64.getDecoder().decode(playBase64), Token.Playload.class);
		}
		
		static public String secretToBase64(String headBase64 ,String playloadBase64 ,String secret) {
			String signature = (headBase64 + CONCAT_CHARACTER + playloadBase64 + CONCAT_CHARACTER + secret);
			return Base64.getEncoder().encodeToString(SHA256Encode(signature));
		}
		
		/*
		 * 使用SHA256加密
		 */
		static private  byte[] SHA256Encode(String str) {
			try {
				MessageDigest digest = MessageDigest.getInstance("SHA-256");
				digest.update(str.getBytes());
				return digest.digest();
			} catch (NoSuchAlgorithmException e) {
				// 
			}
			return null;
		}
		
		static private String objToBase64(Object obj) {
			return Base64.getEncoder().encodeToString(BeanUtil.toJsonAsBytes(obj));
		}
		
	}
}
